// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Classes:  Common Object Security class
**
**
===========================================================*/

using Microsoft.Win32;
using System;
using System.Collections;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;

namespace System.Security.AccessControl
{

    [PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
    public abstract class CommonObjectSecurity: ObjectSecurity
    {
        #region Constructors

        protected CommonObjectSecurity( bool isContainer )
            : base( isContainer, false )
        {
        }

        internal CommonObjectSecurity( CommonSecurityDescriptor securityDescriptor )
            : base( securityDescriptor )
        {
        }

        #endregion
        
        #region Private Methods

        private AuthorizationRuleCollection GetRules( bool access, bool includeExplicit, bool includeInherited, System.Type targetType )
        {
            ReadLock();

            try
            {
                AuthorizationRuleCollection result = new AuthorizationRuleCollection();

                if ( !SecurityIdentifier.IsValidTargetTypeStatic( targetType ))
                {
                    throw new ArgumentException(
                        Environment.GetResourceString("Arg_MustBeIdentityReferenceType"), 
                        "targetType" );
                }

                CommonAcl acl = null;

                if ( access )
                {
                    if (( _securityDescriptor.ControlFlags & ControlFlags.DiscretionaryAclPresent ) != 0)
                    {
                        acl = _securityDescriptor.DiscretionaryAcl;
                    }
                }
                else // !access == audit
                {
                    if (( _securityDescriptor.ControlFlags & ControlFlags.SystemAclPresent ) != 0)
                    {
                        acl = _securityDescriptor.SystemAcl;
                    }
                }

                if ( acl == null )
                {
                    //
                    // The required ACL was not present; return an empty collection.
                    //
                    return result;
                }

                IdentityReferenceCollection irTarget = null;

                if ( targetType != typeof( SecurityIdentifier ))
                {
                    IdentityReferenceCollection irSource = new IdentityReferenceCollection( acl.Count );

                    for ( int i = 0; i < acl.Count; i++ )
                    {
                        //
                        // Calling the indexer on a common ACL results in cloning,
                        // (which would not be the case if we were to use the internal RawAcl property)
                        // but also ensures that the resulting order of ACEs is proper
                        // However, this is a big price to pay - cloning all the ACEs just so that
                        // the canonical order could be ascertained just once.
                        // A better way would be to have an internal method that would canonicalize the ACL
                        // and call it once, then use the RawAcl.
                        //
                        CommonAce ace = acl[i] as CommonAce;
                        if (AceNeedsTranslation(ace, access, includeExplicit, includeInherited))
                        {
                            irSource.Add( ace.SecurityIdentifier );
                        }
                    }

                    irTarget = irSource.Translate( targetType );
                }

                int targetIndex = 0;
                for ( int i = 0; i < acl.Count; i++ )
                {
                    //
                    // Calling the indexer on a common ACL results in cloning,
                    // (which would not be the case if we were to use the internal RawAcl property)
                    // but also ensures that the resulting order of ACEs is proper
                    // However, this is a big price to pay - cloning all the ACEs just so that
                    // the canonical order could be ascertained just once.
                    // A better way would be to have an internal method that would canonicalize the ACL
                    // and call it once, then use the RawAcl.
                    //

                    CommonAce ace = acl[i] as CommonAce;
                    if (AceNeedsTranslation(ace, access, includeExplicit, includeInherited))
                    {
                        IdentityReference iref = ( targetType == typeof(SecurityIdentifier )) ? ace.SecurityIdentifier : irTarget[targetIndex++];

                        if ( access )
                        {
                            AccessControlType type;

                            if (ace.AceQualifier == AceQualifier.AccessAllowed)
                            {
                                type = AccessControlType.Allow;
                            }
                            else
                            {
                                type = AccessControlType.Deny;
                            }

                            result.AddRule(
                                AccessRuleFactory(
                                    iref, 
                                    ace.AccessMask, 
                                    ace.IsInherited, 
                                    ace.InheritanceFlags, 
                                    ace.PropagationFlags, 
                                    type ));
                        }
                        else
                        {
                            result.AddRule(
                                AuditRuleFactory(
                                    iref, 
                                    ace.AccessMask, 
                                    ace.IsInherited, 
                                    ace.InheritanceFlags, 
                                    ace.PropagationFlags, 
                                    ace.AuditFlags));
                        }
                    }
                }

                return result;
            }
            finally
            {
                ReadUnlock();
            }
        }

        private bool AceNeedsTranslation(CommonAce ace, bool isAccessAce, bool includeExplicit, bool includeInherited)
        {
            if ( ace == null )
            {
                //
                // Only consider common ACEs
                //

                return false;
            }

            if ( isAccessAce )
            {
                if ( ace.AceQualifier != AceQualifier.AccessAllowed &&
                    ace.AceQualifier != AceQualifier.AccessDenied )
                {
                    return false;
                }
            }
            else
            {
                if ( ace.AceQualifier != AceQualifier.SystemAudit )
                {
                    return false;
                }
            }

            if (( includeExplicit && 
                (( ace.AceFlags & AceFlags.Inherited ) == 0 )) ||
                ( includeInherited && 
                (( ace.AceFlags & AceFlags.Inherited ) != 0 )))
            {
                return true;
            }

            return false;
        }
              
        //
        // Modifies the DACL
        //
        protected override bool ModifyAccess(AccessControlModification modification, AccessRule rule, out bool modified)
        {
            if (rule == null)
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();
            try
            {
                bool result = true;

                if ( _securityDescriptor.DiscretionaryAcl == null )
                {
                    if ( modification == AccessControlModification.Remove || 
                        modification == AccessControlModification.RemoveAll ||
                        modification == AccessControlModification.RemoveSpecific )
                    {
                        modified = false;
                        return result;
                    }

                    _securityDescriptor.DiscretionaryAcl = new DiscretionaryAcl( IsContainer, IsDS, GenericAcl.AclRevision, 1 );
                    _securityDescriptor.AddControlFlags(ControlFlags.DiscretionaryAclPresent);
                }

                SecurityIdentifier sid = rule.IdentityReference.Translate( typeof( SecurityIdentifier )) as SecurityIdentifier;

                if ( rule.AccessControlType == AccessControlType.Allow )
                {
                    switch ( modification )
                    {
                        case AccessControlModification.Add:
                            _securityDescriptor.DiscretionaryAcl.AddAccess( AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.Set:
                            _securityDescriptor.DiscretionaryAcl.SetAccess( AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.Reset:
                            _securityDescriptor.DiscretionaryAcl.RemoveAccess( AccessControlType.Deny, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0 );
                            _securityDescriptor.DiscretionaryAcl.SetAccess( AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.Remove:
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess( AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.RemoveAll:
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess( AccessControlType.Allow, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0 );
                            if ( result == false )
                            {
                                Contract.Assert( false, "Invalid operation" );
                                throw new SystemException();
                            }

                            break;

                        case AccessControlModification.RemoveSpecific:
                            _securityDescriptor.DiscretionaryAcl.RemoveAccessSpecific( AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        default :
                            throw new ArgumentOutOfRangeException(
                                "modification",
                                Environment.GetResourceString( "ArgumentOutOfRange_Enum" ));
                    }
                }
                else if ( rule.AccessControlType == AccessControlType.Deny )
                {
                    switch ( modification )
                    {
                        case AccessControlModification.Add :
                            _securityDescriptor.DiscretionaryAcl.AddAccess( AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.Set :
                            _securityDescriptor.DiscretionaryAcl.SetAccess( AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.Reset :
                            _securityDescriptor.DiscretionaryAcl.RemoveAccess( AccessControlType.Allow, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0 );
                            _securityDescriptor.DiscretionaryAcl.SetAccess( AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.Remove :
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        case AccessControlModification.RemoveAll :
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Deny, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0 );
                            if ( result == false )
                            {
                                Contract.Assert( false, "Invalid operation" );
                                throw new SystemException();
                            }

                            break;

                        case AccessControlModification.RemoveSpecific :
                            _securityDescriptor.DiscretionaryAcl.RemoveAccessSpecific( AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                            break;

                        default :
                            throw new ArgumentOutOfRangeException(
                                "modification",
                                Environment.GetResourceString( "ArgumentOutOfRange_Enum" ));
                    }
                }
                else
                {
                    Contract.Assert( false, "rule.AccessControlType unrecognized" );
                    throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)rule.AccessControlType), "rule.AccessControlType" );
                }

                modified = result;
                AccessRulesModified |= modified;
                return result;
            }
            finally
            {
                WriteUnlock();
            }
        }

        //
        // Modifies the SACL
        //

        protected override bool ModifyAudit( AccessControlModification modification, AuditRule rule, out bool modified )
        {
            if (rule == null)
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();
            try
            {
                bool result = true;

                if ( _securityDescriptor.SystemAcl == null )
                {
                    if ( modification == AccessControlModification.Remove || 
                        modification == AccessControlModification.RemoveAll ||
                        modification == AccessControlModification.RemoveSpecific )
                    {
                        modified = false;
                        return result;
                    }

                    _securityDescriptor.SystemAcl = new SystemAcl( IsContainer, IsDS, GenericAcl.AclRevision, 1 );
                    _securityDescriptor.AddControlFlags(ControlFlags.SystemAclPresent);
                }

                SecurityIdentifier sid = rule.IdentityReference.Translate( typeof( SecurityIdentifier )) as SecurityIdentifier;

                switch ( modification )
                {
                    case AccessControlModification.Add :
                        _securityDescriptor.SystemAcl.AddAudit( rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                        break;

                    case AccessControlModification.Set :
                        _securityDescriptor.SystemAcl.SetAudit( rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                        break;

                    case AccessControlModification.Reset :
                        _securityDescriptor.SystemAcl.SetAudit( rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                        break;

                    case AccessControlModification.Remove :
                        result = _securityDescriptor.SystemAcl.RemoveAudit( rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                        break;

                    case AccessControlModification.RemoveAll :
                        result = _securityDescriptor.SystemAcl.RemoveAudit( AuditFlags.Failure | AuditFlags.Success, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0 );
                        if ( result == false )
                        {
                            throw new InvalidProgramException();
                        }

                        break;

                    case AccessControlModification.RemoveSpecific :
                        _securityDescriptor.SystemAcl.RemoveAuditSpecific(rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags );
                        break;

                    default :
                        throw new ArgumentOutOfRangeException(
                            "modification",
                            Environment.GetResourceString( "ArgumentOutOfRange_Enum" ));
                }

                modified = result;
                AuditRulesModified |= modified;
                return result;
            }
            finally
            {
                WriteUnlock();
            }
        }

        #endregion

        #region Protected Methods
      
        #endregion

        #region Public Methods

        protected void AddAccessRule( AccessRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAccess( AccessControlModification.Add, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void SetAccessRule( AccessRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAccess( AccessControlModification.Set, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void ResetAccessRule( AccessRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAccess( AccessControlModification.Reset, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }

            return;
        }

        protected bool RemoveAccessRule(AccessRule rule)
        {
            if ( rule == null )
            {
                throw new ArgumentNullException( "rule" );
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                if ( _securityDescriptor == null )
                {
                    return true;
                }

                bool modified;
                return ModifyAccess( AccessControlModification.Remove, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void RemoveAccessRuleAll( AccessRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                if ( _securityDescriptor == null )
                {
                    return;
                }

                bool modified;
                ModifyAccess( AccessControlModification.RemoveAll, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }

            return;
        }

        protected void RemoveAccessRuleSpecific( AccessRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException("rule");
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                if ( _securityDescriptor == null )
                {
                    return;
                }

                bool modified;
                ModifyAccess( AccessControlModification.RemoveSpecific, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void AddAuditRule( AuditRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException( "rule" );
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAudit( AccessControlModification.Add, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void SetAuditRule( AuditRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException( "rule" );
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAudit( AccessControlModification.Set, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected bool RemoveAuditRule( AuditRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException( "rule" );
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                return ModifyAudit( AccessControlModification.Remove, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void RemoveAuditRuleAll( AuditRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException( "rule" );
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAudit( AccessControlModification.RemoveAll, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void RemoveAuditRuleSpecific( AuditRule rule )
        {
            if ( rule == null )
            {
                throw new ArgumentNullException( "rule" );
            }
            Contract.EndContractBlock();

            WriteLock();

            try
            {
                bool modified;
                ModifyAudit( AccessControlModification.RemoveSpecific, rule, out modified );
            }
            finally
            {
                WriteUnlock();
            }
        }

        public AuthorizationRuleCollection GetAccessRules( bool includeExplicit, bool includeInherited, System.Type targetType )
        {
            return GetRules( true, includeExplicit, includeInherited, targetType );
        }

        public AuthorizationRuleCollection GetAuditRules( bool includeExplicit, bool includeInherited, System.Type targetType )
        {
            return GetRules( false, includeExplicit, includeInherited, targetType );
        }
        
        #endregion
    }
}
